#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import pwd
import shutil
import time
from contextlib import contextmanager
from functools import wraps
from threading import Thread

import psutil

from common.common import check_command_injection, output_result_file, output_execution_result, retry_when_exception
from common.common import check_path_legal
from common.common import execute_cmd
from common.common_models import ActionResult, SubJobDetails
from common.const import CMDResult, ExecuteResultEnum, ParamConstant, RpcToolInterface
from common.const import SysData
from common.exception.common_exception import ErrCodeException
from common.util.exec_utils import exec_overwrite_file
from generaldb.gbase8a import LOGGER
from generaldb.gbase8a.common.const import JsonConstant
from generaldb.gbase8a.common.const import RpcParamKey, DELETING_PATH_WHITE_LIST, GbasePath


def get_uid_gid(user):
    """
    获取用户id,组id，用户不存在报错
    :param user: 用户名
    :return: uid, gid
    """
    try:
        user_info = pwd.getpwnam(str(user))
    except Exception:
        return "", ""
    group_id = user_info.pw_gid
    user_id = user_info.pw_uid
    return user_id, group_id


def gbase_check_user_name_and_injection(param: str):
    """
    检测用户名是否为空并判断是否有特殊字符
    :param : 用户名
    :return: bool
    """
    if not param:
        return True
    return check_command_injection(param)


def check_ip_in_string(ip_addr: str, line_info: str):
    if ip_addr not in line_info:
        return False
    for values in line_info.split("|"):
        if ip_addr == values.strip():
            return True
    return False


def get_value_by_ip_and_keyword(info_lines, ip_address, key_string):
    ip_index = -1
    catalogue_line = ""
    for line in info_lines.split("\n"):
        if key_string in line:
            catalogue_line = line
            break
    for idx, val in enumerate(catalogue_line.split("|")):
        if key_string in val:
            ip_index = idx
            break
    if ip_index < 0:
        LOGGER.error("Failed get value of %s", key_string)
        return ''
    local_ip = ip_address
    for line in info_lines.split("\n"):
        if "." in line and check_ip_in_string(local_ip, line):
            return line.split("|")[ip_index].strip()
    return ''


def get_value_by_keyword_row(info_lines, key_string):
    ip_index = -1
    catalogue_line = ""
    catalogue_row = 0
    for row, line in enumerate(info_lines.split("\n")):
        if key_string in line:
            catalogue_line = line
            catalogue_row = row
            break
    for idx, val in enumerate(catalogue_line.split("|")):
        if key_string in val:
            ip_index = idx
            break
    if ip_index < 0:
        LOGGER.error("Failed get value of %s", key_string)
        return []
    ret_values = []
    for row, line in enumerate(info_lines.split("\n")):
        if "|" in line and row > catalogue_row:
            ret_values.append(line.split("|")[ip_index].strip())
    return ret_values


def get_env_variable(str_env_variable: str):
    env_variable = ''
    input_dict = json.loads(SysData.SYS_STDIN)
    if input_dict.get(str_env_variable):
        env_variable = input_dict.get(str_env_variable)

    return env_variable


def exec_rc_tool_cmd(unique_id, interface_name, param_dict):
    """
    执行rc_tool命令
    @@param cmd: 需要执行的命令
    @@param in_param: 需要写入输入文件的命令参数
    @@param unique_id: 输入输出文件唯一标识
    @@return result:bool 命令执行结果
    @@return output:string 命令输出
    """

    def clear_file(path):
        if not check_path_legal(path, GbasePath.GBASE_FILESYSTEM_MOUNT_PATH):
            return
        if os.path.isfile(path):
            os.remove(path)

    input_file_path = os.path.join(ParamConstant.PARAM_FILE_PATH, ParamConstant.INPUT_FILE_PREFFIX + unique_id)
    output_file_path = os.path.join(ParamConstant.RESULT_PATH, ParamConstant.OUTPUT_FILE_PREFFIX + unique_id)
    exec_overwrite_file(input_file_path, param_dict)
    cmd = f"{os.path.join(ParamConstant.BIN_PATH, 'rpctool.sh')} {interface_name} {input_file_path} {output_file_path}"
    try:
        ret, std_out, std_err = execute_cmd(cmd)
    except Exception as err:
        raise err
    finally:
        # 执行命令后删除输入文件
        clear_file(input_file_path)

    if ret != CMDResult.SUCCESS:
        return {}

    # 读取文件成功后删除文件
    try:
        with open(output_file_path, "r", encoding='utf-8') as tmp:
            result = json.load(tmp)
    except Exception as err:
        raise err
    finally:
        clear_file(output_file_path)

    return result


@retry_when_exception(delay=5)
def job_report(job_id, sub_job_details):
    return rpc_tool_with_lock(job_id, RpcToolInterface.REPORT_JOB_DETAIL, sub_job_details)


def report_job_details(job_id: str, sub_job_details: dict):
    try:
        result_info = job_report(job_id, sub_job_details)
    except Exception as err:
        LOGGER.exception(f"Invoke rpc_tool interface exception, err: {err}.")
        return False
    if not result_info:
        return False
    ret_code = result_info.get("code", -1)
    if ret_code != 0:
        LOGGER.error("Invoke rpc_tool interface failed, result code: %s.", ret_code)
        return False
    return True


def rpc_tool_with_lock(job_id, interface, report_info):
    def clear_file(path):
        if os.path.isfile(path):
            os.remove(path)

    timestamp = time.time()
    unique_id = f"{job_id}_{timestamp}"
    input_file_path = os.path.join(RpcParamKey.PARAM_FILE_PATH, RpcParamKey.INPUT_FILE_PREFFIX + unique_id)
    output_file_path = os.path.join(RpcParamKey.RESULT_PATH, RpcParamKey.OUTPUT_FILE_PREFFIX + unique_id)
    output_execution_result(input_file_path, report_info)

    cmd = f"sh {RpcParamKey.RPC_TOOL} {interface} {input_file_path} {output_file_path}"
    LOGGER.info(f"rpc_tool_with_lock:{cmd}")
    # 执行命令后不论结果都需要删除输入文件
    try:
        ret, std_out, std_err = execute_cmd(cmd)
        LOGGER.info(f"rpc_tool_with_lock1:{ret},{std_out},{std_err}")
    except Exception as err:
        raise err
    finally:
        clear_file(input_file_path)
    LOGGER.info(f"rpc_tool_with_lock:{ret},{std_out},{std_err}")
    if ret != CMDResult.SUCCESS.value:
        err_info = f"Invoke rpc_tool script failed, std_err: {std_err}."
        raise Exception(err_info)

    # 不管读取文件是否成功都需要删除文件
    try:
        with open(output_file_path, "r", encoding='utf-8') as tmp:
            result = json.load(tmp)
    except Exception as err:
        raise err
    finally:
        clear_file(output_file_path)
    return result


def lookup_process(key_list: list):
    pids = psutil.pids()
    for pid in pids:
        try:
            result = psutil.Process(pid).cmdline()
        except Exception:
            LOGGER.error("Pid does not exist.")
            continue
        if len(result) < len(key_list):
            continue
        for i in range(len(key_list) - 1):
            if not key_list[i]:
                continue
            if key_list[i] != result[i]:
                break
        return pid
    return 0


def look_up_gcrcman_process(gcrcman_path):
    if not gcrcman_path:
        return 0
    pids = psutil.pids()
    for pid in pids:
        try:
            result = psutil.Process(pid).cmdline()
        except Exception:
            LOGGER.warning("Pid %s does not exist.", pid)
            continue
        if gcrcman_path in result:
            LOGGER.info("Gcrcman process exists.")
            return pid
    return 0


def record_result(ignore_normal_record=False, ignore_exception_record=False):
    """
    输出执行结果装饰器，任务上报输出结果统一入口
    :param output:输出结构体
    :param ignore_normal_record:（执行结果正常）是否忽略上报
    :param ignore_exception_record: （执行结果异常）是否忽略上报
    """

    def decorate(func):
        @wraps(func)
        def inner(obj, *args):
            """
            :param obj: restore_service实例
            """
            pid = obj.pid
            excepted = False
            code = ExecuteResultEnum.SUCCESS
            try:
                func(obj, *args)
            except ErrCodeException as ex:
                excepted = True
                err_code, err_msg = ex.error_code, ex.error_message
                LOGGER.exception(f"Execute task failed, function name: {func.__name__}, error code: {err_code}, "
                                 f"error message: {err_msg}.")
            except Exception as ex:
                excepted = True
                LOGGER.exception(f"Execute task failed. Function Name: {func.__name__}.")
                err_code = ExecuteResultEnum.INTERNAL_ERROR
                err_msg = f"Execute task failed. Error Message: {str(ex)}."
            else:
                err_code = ExecuteResultEnum.SUCCESS
                err_msg = ""
            output = obj.result
            if ignore_normal_record and excepted == ignore_exception_record:
                return output
            if excepted:
                code = ExecuteResultEnum.INTERNAL_ERROR
            if output is None:
                output = ActionResult(code=code)
            if isinstance(output, ActionResult):
                output.code = code
                output.body_err = err_code
                output.message = err_msg
                output = output.dict(by_alias=True)
            if isinstance(output, SubJobDetails):
                output = output.dict(by_alias=True)
            try:
                output_result_file(pid, output)
            except Exception as not_named_e:
                LOGGER.error("Write output file failed with err:%s", str(not_named_e))
                raise not_named_e
            return output

        return inner

    return decorate


def check_path(path):
    pass


@contextmanager
def report_progress(report_step, *report_args):
    rp_thread = Thread(target=report_step, args=report_args)
    rp_thread.setDaemon(True)
    rp_thread.start()
    yield rp_thread
    rp_thread.join()


def remove_file_dir_link(path_):
    realpath = ''
    for parent_dir in DELETING_PATH_WHITE_LIST:
        ret = check_path_legal(path_, parent_dir)
        if not ret:
            continue
        realpath = path_
    if not realpath:
        return False
    if not os.path.exists(realpath):
        return True
    if os.path.isfile(realpath) or os.path.islink(realpath):
        try:
            os.remove(realpath)
        except Exception:
            return False
    elif os.path.isdir(realpath):
        try:
            shutil.rmtree(realpath)
        except Exception:
            return False
    return True


def check_gbase_path(path):
    realpath = ""
    for parent_dir in DELETING_PATH_WHITE_LIST:
        ret = check_path_legal(path, parent_dir)
        if not ret:
            continue
        realpath = path
    if not realpath:
        return False
    return True


def get_local_uuid():
    if not os.path.exists(JsonConstant.HOSTSN_FILE_PATH):
        return ''
    with open(JsonConstant.HOSTSN_FILE_PATH, 'r') as f_object:
        hostsn = f_object.read()
    hostsn = hostsn.strip("\n")
    return hostsn


def check_path_in_white_list(path_: str, white_list: list):
    try:
        real_path = os.path.realpath(path_)
    except Exception as e:
        return False, ''

    if check_command_injection(real_path):
        return False, ''

    for path in white_list:
        if real_path.find(path) == 0:
            return True, real_path
    if f"{real_path}/" in white_list:
        return True, real_path
    return False, ''


def parse_custom_str_to_dict(custom_str) -> dict:
    """
    转换自定义参数为字典
    :param custom_str: "field=value,field1=value1,……”
    :return: {field:value,field1:value,……}
    """
    custom_list = [kv_str.split("=", 1) for kv_str in custom_str.split(",")]
    custom_dict = {kv_list[0]: kv_list[1] for kv_list in custom_list if len(kv_list) > 1}
    return custom_dict
